/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "vsf.h"

struct vsfip_netdrv_cb_t vsfusbh_ecm_cb;

struct vsfusbh_ecm_t
{
	struct vsfusbh_cdc_t cdc;

	struct vsfip_netdrv_t *netdrv;
	uint8_t evt[16];
	uint8_t iMAC;
	uint16_t max_segment_size;
};

static int hex_to_bin(char ch)
{
	if ((ch >= '0') && (ch <= '9'))
		return ch - '0';
	ch = tolower(ch);
	if ((ch >= 'a') && (ch <= 'f'))
		return ch - 'a' + 10;
	return -1;
}

static vsf_err_t vsfusbh_cdcecm_on_desc(struct vsfusbh_cdc_t *cdc,
		struct usb_class_interface_descriptor_t *desc)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)cdc;
	switch (desc->bDescriptorSubType)
	{
	case 0x0F:		// Ethernet Networking Functional Descriptor
		{
			struct usb_cdc_ecm_descriptor_t *ecm_desc =
				(struct usb_cdc_ecm_descriptor_t *)desc;
			ecm->iMAC = ecm_desc->iMACAddress;
			ecm->max_segment_size = GET_LE_U16(ecm_desc->wMaxSegmentSize);
			if (ecm->max_segment_size > (VSFIP_CFG_MTU + VSFIP_CFG_NETIF_HEADLEN))
				return VSFERR_FAIL;
		}
		break;
	}
	return VSFERR_NONE;
}

static void vsfusbh_cdcecm_on_event(struct vsfusbh_cdc_t *cdc)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)cdc;
	bool connected;

	switch (ecm->evt[1])
	{
	case 0x00:			// NETWORK_CONNECTION
		connected = vsfip_netdrv_connected(ecm->netdrv);
		if (connected && (ecm->evt[2] == 0))
		{
			vsfdbg_prints("cdc_ecm_event: NETWORK_CONNECTION Disconnected" VSFCFG_DEBUG_LINEEND);
			vsfip_netdrv_disconnect(ecm->netdrv);
		}
		else if (!connected && (ecm->evt[2] != 0))
		{
			vsfdbg_prints("cdc_ecm_event: NETWORK_CONNECTION Connected" VSFCFG_DEBUG_LINEEND);
			vsfip_netdrv_connect(ecm->netdrv);
		}
		break;
	case 0x2A:			// CONNECTION_SPEED_CHANGE
//		vsfdbg_prints("cdc_ecm_event: CONNECTION_SPEED_CHANGE" VSFCFG_DEBUG_LINEEND);
		break;
	default:
		vsfdbg_printf("cdc_ecm_event: unknown(%d)" VSFCFG_DEBUG_LINEEND, ecm->evt[0]);
		break;
	}
}

static void vsfusbh_cdcecm_on_io(struct vsfusbh_cdc_t *cdc, bool isin,
		struct vsfhcd_urb_t *urb)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)cdc;
	struct vsfip_netdrv_t *netdrv = ecm->netdrv;

	if (isin)
	{
		netdrv->rxbuf.buffer.size = urb->actual_length;
		vsfip_netdrv_on_inputted(netdrv, urb->status);
	}
	else
	{
		vsfip_netdrv_on_outputted(netdrv, urb->status);
	}
}

static const struct vsfusbh_cdc_cb_t vsfusbh_ecm_cdccb =
{
	.on_desc = vsfusbh_cdcecm_on_desc,
	.on_event = vsfusbh_cdcecm_on_event,
	.on_io = vsfusbh_cdcecm_on_io,
};

static vsf_err_t vsfusbh_ecm_init_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)pt->user_data;
	struct vsfusbh_cdc_t *cdc = &ecm->cdc;
	struct vsfip_netdrv_t *netdrv = ecm->netdrv;
	struct vsfusbh_t *usbh = cdc->usbh;
	vsf_err_t err;

	vsfsm_pt_begin(pt);

	if (vsfsm_crit_enter(&cdc->dev->ep0_crit, pt->sm))
		vsfsm_pt_wfe(pt, VSFSM_EVT_EP0_CRIT);

	if (!vsfusbh_alloc_urb_buffer(cdc->ctrl_urb, 2 + 12 * 2)) goto ret_failure;
	err = vsfusbh_get_descriptor(usbh, cdc->ctrl_urb, USB_DT_STRING, ecm->iMAC);
	vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
	if ((cdc->ctrl_urb->status != URB_OK) ||
			(cdc->ctrl_urb->actual_length != cdc->ctrl_urb->transfer_length))
		goto ret_failure_crit;

	{
		char *str = (char *)cdc->ctrl_urb->transfer_buffer + 2;
		for (int i = 0; i < VSFIP_ETH_ADDRLEN; i++, str += 4)
			netdrv->macaddr.addr.s_addr_buf[i] = (hex_to_bin(str[0]) << 4) | (hex_to_bin(str[2]) << 0);
	}
	vsfusbh_free_urb_buffer(cdc->ctrl_urb);
	netdrv->macaddr.size = VSFIP_ETH_ADDRLEN;
	vsfdbg_printf("cdc_cdc: MAC is %02X:%02X:%02X:%02X:%02X:%02X" VSFCFG_DEBUG_LINEEND,
		netdrv->macaddr.addr.s_addr_buf[0], netdrv->macaddr.addr.s_addr_buf[1],
		netdrv->macaddr.addr.s_addr_buf[2], netdrv->macaddr.addr.s_addr_buf[3],
		netdrv->macaddr.addr.s_addr_buf[4], netdrv->macaddr.addr.s_addr_buf[5]);

	err = vsfusbh_set_interface(usbh, cdc->ctrl_urb, cdc->data_iface, 0);
	if (err) goto ret_error_crit;
	vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
	if (cdc->ctrl_urb->status != URB_OK) goto ret_failure_crit;

	err = vsfusbh_set_interface(usbh, cdc->ctrl_urb, cdc->data_iface, 1);
	if (err) goto ret_error_crit;
	vsfsm_pt_wfe(pt, VSFSM_EVT_URB_COMPLETE);
	if (cdc->ctrl_urb->status != URB_OK) goto ret_failure_crit;

	vsfsm_crit_leave(&cdc->dev->ep0_crit);

	cdc->pt.thread = vsfusbh_cdc_thread;
	vsfsm_pt_init(&cdc->sm, &cdc->pt);
	vsfsm_pt_end(pt);
	return VSFERR_NONE;

ret_error_crit:
	vsfsm_crit_leave(&cdc->dev->ep0_crit);
	goto ret_error;
ret_failure_crit:
	vsfsm_crit_leave(&cdc->dev->ep0_crit);
ret_failure:
	err = VSFERR_FAIL;
ret_error:
	vsfusbh_remove_interface(cdc->usbh, cdc->dev, cdc->ifs);
	return err;
}

// netdrv
static vsf_err_t vsfusbh_ecm_netdrv_init(struct vsfip_netdrv_t *netdrv)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)netdrv->param;

	netdrv->mtu = ecm->max_segment_size - VSFIP_ETH_HEADSIZE;
	netdrv->header_size = VSFIP_ETH_HEADSIZE;
	netdrv->hwtype = VSFIP_ETH_HWTYPE;
	return VSFERR_NONE;
}

static vsf_err_t vsfusbh_ecm_netdrv_fini(struct vsfip_netdrv_t *netdrv)
{
	return VSFERR_NONE;
}

static vsf_err_t vsfusbh_ecm_netdrv_output(struct vsfip_netdrv_t *netdrv)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)netdrv->param;
	struct vsfusbh_t *usbh = ecm->cdc.usbh;
	struct vsfhcd_urb_t *urb = ecm->cdc.out_urb;

	urb->transfer_buffer = netdrv->txbuf.buffer.buffer;
	urb->transfer_length = netdrv->txbuf.buffer.size;
	urb->free_buffer = netdrv->adapter->free_buf;
	urb->free_buffer_param = netdrv->txbuf.netbuf;
	return vsfusbh_submit_urb(usbh, urb);
}

static vsf_err_t vsfusbh_ecm_netdrv_input(struct vsfip_netdrv_t *netdrv)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)netdrv->param;
	struct vsfusbh_t *usbh = ecm->cdc.usbh;
	struct vsfhcd_urb_t *urb = ecm->cdc.in_urb;

	urb->transfer_buffer = netdrv->rxbuf.buffer.buffer;
	urb->transfer_length = netdrv->rxbuf.buffer.size;
	urb->free_buffer = netdrv->adapter->free_buf;
	urb->free_buffer_param = netdrv->rxbuf.netbuf;
	return vsfusbh_submit_urb(usbh, urb);
}

static const struct vsfip_link_op_t vsfusbh_ecm_link_op =
{
	vsfusbh_ecm_netdrv_init, vsfusbh_ecm_netdrv_fini,
	vsfusbh_ecm_netdrv_output, vsfusbh_ecm_netdrv_input, vsfip_eth_routable
};

static void *vsfusbh_ecm_probe(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, struct vsfusbh_ifs_t *ifs,
		const struct vsfusbh_device_id_t *id)
{
	struct vsfusbh_ecm_t *ecm;
	struct vsfusbh_cdc_t *cdc;
	struct vsfip_netdrv_t *netdrv;

	netdrv = vsfip_netdrv_new(sizeof(*ecm), &vsfusbh_ecm_link_op, &vsfusbh_ecm_cb);
	if (netdrv == NULL) return NULL;
	ecm = (struct vsfusbh_ecm_t *)netdrv->param;
	memset(ecm, 0, sizeof(struct vsfusbh_ecm_t));
	ecm->netdrv = netdrv;

	cdc = &ecm->cdc;
	cdc->cb = &vsfusbh_ecm_cdccb;
	cdc->evtbuf.buffer = ecm->evt;
	cdc->evtbuf.size = sizeof(ecm->evt);
	if (vsfusbh_cdc_init(&ecm->cdc, usbh, dev, ifs))
	{
		vsf_bufmgr_free(ecm);
		return NULL;
	}

	cdc->pt.thread = vsfusbh_ecm_init_thread;
	cdc->pt.user_data = ecm;
	vsfsm_pt_init(&cdc->sm, &cdc->pt);
	return ecm;
}

static void vsfusbh_ecm_disconnect(struct vsfusbh_t *usbh,
		struct vsfusbh_device_t *dev, void *priv)
{
	struct vsfusbh_ecm_t *ecm = (struct vsfusbh_ecm_t *)priv;
	struct vsfip_netdrv_t *netdrv = ecm->netdrv;

	vsfusbh_cdc_fini(&ecm->cdc);

	netdrv->tofree = true;
	if (vsfip_netdrv_connected(netdrv))
		vsfip_netdrv_disconnect(netdrv);
	else
		vsfip_netdrv_free(netdrv);
}

static const struct vsfusbh_device_id_t vsfusbh_ecm_id_table[] =
{
	{
		.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
			USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL,
		.bInterfaceClass = USB_CLASS_COMM,
		.bInterfaceSubClass = 6,		// ECM
		.bInterfaceProtocol = 0x00,
	},
	{0},
};

const struct vsfusbh_class_drv_t vsfusbh_ecm_drv =
{
	.name = "cdc_ecm",
	.id_table = vsfusbh_ecm_id_table,
	.probe = vsfusbh_ecm_probe,
	.disconnect = vsfusbh_ecm_disconnect,
};

